/** @file
This file contains functions designed to update platform SecureBoot profile

@copyright
 Copyright (c) 2013 - 2016 Intel Corporation. All rights reserved
 This software and associated documentation (if any) is furnished
 under a license and may only be used or copied in accordance
 with the terms of the license. Except as permitted by the
 license, no part of this software or documentation may be
 reproduced, stored in a retrieval system, or transmitted in any
 form or by any means without the express written consent of
 Intel Corporation.
 This file contains a 'Sample Driver' and is licensed as such
 under the terms of your license agreement with Intel or your
 vendor. This file may be modified by the user, subject to
 the additional terms of the license agreement.

@par Specification Reference:
**/

#include "SecureBootPlatformConfig.h"
#include "SecureBootConfigImpl.h"


GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID  EFI_Flash_CertGuid                               = { 0x9ec875c2, 0x655c, 0x4f77, { 0xbf, 0x11, 0xbd, 0xc9, 0x24, 0xc6, 0x61, 0x61 } };
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID  KEK_ANDROID_Guid                                 = { 0x1932072d, 0x93d4, 0x46b7, { 0x87, 0x00, 0x00, 0x8f, 0x10, 0x04, 0x03, 0xd6 } };
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID  db_ANDROID_Guid                                  = { 0x12b13ef6, 0x4c14, 0x4534, { 0xbe, 0x97, 0x94, 0xde, 0x23, 0xdc, 0xb8, 0x55 } };
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID  Intel_OwnerGuid                                  = { 0x6E859388, 0x84A9, 0x47c3, { 0x92, 0xE1, 0xAD, 0x14, 0xE5, 0x43, 0xD1, 0x1C } };
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID  MS_OwnerGuid                                     = { 0x77fa9abd, 0x0359, 0x4d32, { 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b } };
GLOBAL_REMOVE_IF_UNREFERENCED EFI_GUID  AND_OwnerGuid                                    = { 0xA903F106, 0xC31D, 0x4BE8, { 0x97, 0xEA, 0xB2, 0xC0, 0xE9, 0xE0, 0x90, 0x0C } };

/**
  Internal helper function to delete a Variable given its name and GUID, NO authentication
  required.

  @param[in]      VariableName            Name of the Variable.
  @param[in]      VendorGuid              GUID of the Variable.

  @retval EFI_SUCCESS              Variable deleted successfully.
  @retval Others                   The driver failed to start the device.

**/
extern
EFI_STATUS
DeleteVariable (
  IN  CHAR16                    *VariableName,
  IN  EFI_GUID                  *VendorGuid
  );

/**
  Create a time based data payload by concatenating the EFI_VARIABLE_AUTHENTICATION_2
  descriptor with the input data. NO authentication is required in this function.
  
  @param[in, out]   DataSize       On input, the size of Data buffer in bytes.
                                   On output, the size of data returned in Data
                                   buffer in bytes.
  @param[in, out]   Data           On input, Pointer to data buffer to be wrapped or 
                                   pointer to NULL to wrap an empty payload.
                                   On output, Pointer to the new payload date buffer allocated from pool,
                                   it's caller's responsibility to free the memory when finish using it. 

  @retval EFI_SUCCESS              Create time based payload successfully.
  @retval EFI_OUT_OF_RESOURCES     There are not enough memory resourses to create time based payload.
  @retval EFI_INVALID_PARAMETER    The parameter is invalid.
  @retval Others                   Unexpected error happens.

**/
extern
EFI_STATUS
CreateTimeBasedPayload (
  IN OUT UINTN            *DataSize,
  IN OUT UINT8            **Data
  );

/**
  Function to enabled/disable SecureBootCustomMode
  @param[in]      CustomEnable            Mode to set
  
  @retval EFI_SUCCESS              Variable set successfully
  @retval Others                   Failed to update custom mode variable

**/
EFI_STATUS
UpdateCustomMode (
  IN UINT8            CustomEnable
  )
{
  EFI_STATUS  Status;

  Status = gRT->SetVariable (
                  EFI_CUSTOM_MODE_NAME,
                  &gEfiCustomModeEnableGuid,
                  EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  sizeof (UINT8),
                  &CustomEnable
                  );
  return Status;
}

/**
  Function to Load Secure Keys from firmware volume 
  given the image GUID 

  @param[in]          ImageGuid                Image guid of the file.
  @param[in,out]      DefaultsBuffer           Buffer to return file
  @param[in,out]      DefaultsBufferSize       File size

  @retval EFI_SUCCESS              Found Key.
  @retval Others                   Key search failed.

**/
EFI_STATUS
GetX509Cert (
  IN   EFI_GUID      *ImageGuid,
  OUT  VOID          **DefaultsBuffer,
  OUT  UINTN         *DefaultsBufferSize
  )
{
  EFI_STATUS                      Status;
  EFI_FIRMWARE_VOLUME2_PROTOCOL   *Fv;
  UINTN                           FvProtocolCount;
  EFI_HANDLE                      *FvHandles;
  UINTN                           Index1;
  UINT32                          AuthenticationStatus;

  *DefaultsBuffer      = NULL;
  *DefaultsBufferSize = 0;

  FvHandles = NULL;
  Status = gBS->LocateHandleBuffer (
                ByProtocol,
                &gEfiFirmwareVolume2ProtocolGuid,
                NULL,
                &FvProtocolCount,
                &FvHandles
                );

  if (!EFI_ERROR (Status)) {
    for (Index1 = 0; Index1 < FvProtocolCount; Index1++) {
      Status = gBS->HandleProtocol (
                      FvHandles[Index1],
                      &gEfiFirmwareVolume2ProtocolGuid,
                      (VOID **) &Fv
                      );
      *DefaultsBufferSize= 0;

      Status = Fv->ReadSection (
                    Fv,
                    ImageGuid,
                    EFI_SECTION_RAW,
                    0,
                    DefaultsBuffer,
                    DefaultsBufferSize,
                    &AuthenticationStatus
                    );

      if (!EFI_ERROR (Status)) {
        Status = EFI_SUCCESS;
        break;
      }
    }
  }
  return Status;
}

/**
  Remove all secureboot keys

  @retval EFI_SUCCESS    Deleted keys successfully.
  @retval Others         Failed to delete keys

**/
EFI_STATUS
RemoveAllSecureBootKeys (
  VOID
)
{
  EFI_STATUS Status;

  Status = DeleteVariable (
             EFI_PLATFORM_KEY_NAME,
             &gEfiGlobalVariableGuid
             );
  Status = DeleteVariable (
             EFI_KEY_EXCHANGE_KEY_NAME,
             &gEfiGlobalVariableGuid
             );
  Status = DeleteVariable (
             EFI_IMAGE_SECURITY_DATABASE,
             &gEfiImageSecurityDatabaseGuid
             );
  Status = DeleteVariable (
             EFI_IMAGE_SECURITY_DATABASE1,
             &gEfiImageSecurityDatabaseGuid
             );

  Status = DeleteVariable (
             EFI_SECURE_BOOT_ENABLE_NAME,
             &gEfiSecureBootEnableDisableGuid
             );

  return Status;
}

// For Secure Boot logo test on Win 10 >>>

/**
  Append signature into Forbidden Database (DBX) from 
  firmware volume

  @param[in]  CertificateGuid     Imageguid of the certificate file
  @param[in]  VariableName        Variable database to put the certificate
  @param[in]  VendorGuid          Variable owner guid
  @param[in]  SignatureOwner      Certificate owner guid

  @retval   EFI_SUCCESS            New X509 is enrolled successfully.
  @retval   EFI_OUT_OF_RESOURCES   Could not allocate needed resources.

**/
EFI_STATUS
AppendX509FromFV(
  IN EFI_GUID                         *CertificateGuid,
  IN  CHAR16                          *VariableName,
  IN EFI_GUID                         *VendorGuid,
  IN EFI_GUID                         *SignatureOwner
  )
{
  EFI_STATUS                        Status;
  VOID                              *Data;
  UINTN                             DataSize;
  UINTN                             SigDBSize;
  UINT32                            Attr;
  UINTN                             X509DataSize;
  VOID                              *X509Data;

  X509DataSize  = 0;
  X509Data      = NULL;
  SigDBSize     = 0;
  DataSize      = 0;
  Data          = NULL;

  Status = GetX509Cert( CertificateGuid, &X509Data,&X509DataSize);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  SigDBSize = X509DataSize;

  Data = AllocateZeroPool (SigDBSize);
  if (Data == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  CopyMem ((UINT8* )Data, X509Data, X509DataSize);

  Attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS 
          | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;

  //
  // Check if signature database entry has been already existed. 
  // If true, use EFI_VARIABLE_APPEND_WRITE attribute to append the 
  // new signature data to original variable
  //    

  Status = gRT->GetVariable(
                  VariableName,
                  VendorGuid,
                  NULL,
                  &DataSize,
                  NULL
                  );

  if (Status == EFI_BUFFER_TOO_SMALL) {
    Attr |= EFI_VARIABLE_APPEND_WRITE;
  } else if (Status != EFI_NOT_FOUND) {
    goto ON_EXIT;
  }  

  Status = gRT->SetVariable(
                  VariableName,
                  VendorGuid,
                  Attr,
                  SigDBSize,
                  Data
                  );

  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

ON_EXIT:
  
  if (Data != NULL) {
    FreePool (Data);
  }

  if (X509Data != NULL) {
    FreePool (X509Data);
  }

  return Status;
}


// For Secure Boot logo test on Win10 <<<


/**
  Enroll a X509 certificate into Signature Database (PK, KEK, DB DBX) from 
  firmware volume

  @param[in]  CertificateGuid     Imageguid of the certificate file
  @param[in]  VariableName        Variable database to put the certificate
  @param[in]  VendorGuid          Variable owner guid
  @param[in]  SignatureOwner      Certificate owner guid

  @retval   EFI_SUCCESS            New X509 is enrolled successfully.
  @retval   EFI_OUT_OF_RESOURCES   Could not allocate needed resources.

**/
EFI_STATUS
EnrollX509FromFV (
  IN EFI_GUID                         *CertificateGuid,
  IN  CHAR16                          *VariableName,
  IN EFI_GUID                         *VendorGuid,
  IN EFI_GUID                         *SignatureOwner
  )
{
  EFI_STATUS                        Status;
  EFI_SIGNATURE_LIST                *SigDBCert;
  EFI_SIGNATURE_DATA                *SigDBCertData;
  VOID                              *Data;
  UINTN                             DataSize;
  UINTN                             SigDBSize;
  UINT32                            Attr;
  UINTN                             X509DataSize;
  VOID                              *X509Data;

  X509DataSize  = 0;
  X509Data      = NULL;
  SigDBSize     = 0;
  DataSize      = 0;
  SigDBCert     = NULL;
  SigDBCertData = NULL;
  Data          = NULL;

  Status = GetX509Cert( CertificateGuid, &X509Data,&X509DataSize);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  SigDBSize = sizeof(EFI_SIGNATURE_LIST) + sizeof(EFI_SIGNATURE_DATA) - 1 + X509DataSize;

  Data = AllocateZeroPool (SigDBSize);
  if (Data == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  //
  // Fill Certificate Database parameters.
  // 
  SigDBCert = (EFI_SIGNATURE_LIST*) Data;
  SigDBCert->SignatureListSize   = (UINT32) SigDBSize;
  SigDBCert->SignatureHeaderSize = 0;
  SigDBCert->SignatureSize = (UINT32) (sizeof(EFI_SIGNATURE_DATA) - 1 + X509DataSize);
  CopyGuid (&SigDBCert->SignatureType, &gEfiCertX509Guid);

  SigDBCertData = (EFI_SIGNATURE_DATA*) ((UINT8* ) SigDBCert + sizeof (EFI_SIGNATURE_LIST));
  CopyGuid (&SigDBCertData->SignatureOwner, SignatureOwner);
  CopyMem ((UINT8* ) (SigDBCertData->SignatureData), X509Data, X509DataSize);
  //
  // Check if signature database entry has been already existed. 
  // If true, use EFI_VARIABLE_APPEND_WRITE attribute to append the 
  // new signature data to original variable
  //    
  Attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS 
          | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
  Status = CreateTimeBasedPayload (&SigDBSize, (UINT8**) &Data);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Fail to create time-based data payload: %r", Status));
    goto ON_EXIT;
  }

  Status = gRT->GetVariable(
                  VariableName,
                  VendorGuid,
                  NULL,
                  &DataSize,
                  NULL
                  );

  if (Status == EFI_BUFFER_TOO_SMALL) {
    Attr |= EFI_VARIABLE_APPEND_WRITE;
  } else if (Status != EFI_NOT_FOUND) {
    goto ON_EXIT;
  }  

  Status = gRT->SetVariable(
                  VariableName,
                  VendorGuid,
                  Attr,
                  SigDBSize,
                  Data
                  );

  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

ON_EXIT:

  if (Data != NULL) {
    FreePool (Data);
  }

  if (X509Data != NULL) {
    FreePool (X509Data);
  }

  return Status;
}

/**
  Locate and enroll keys for selected SecureBootProfile

  @param[in] Profile       The SecureBoot profile selected 

  @retval  EFI_SUCCESS     Profile was successfully loaded
  @retval  Others          Failed 

**/
EFI_STATUS
SetPlatformSecureBootProfile (
IN       UINT8          Profile
)
{
  EFI_STATUS              Status;

  Status         = EFI_SUCCESS;

  UpdateCustomMode( CUSTOM_SECURE_BOOT_MODE );
  RemoveAllSecureBootKeys();

  switch (Profile) {
    case SECURE_BOOT_PROFILE_NONE:
      UpdateCustomMode (STANDARD_SECURE_BOOT_MODE);
      return Status;
      break;
    case SECURE_BOOT_PROFILE_WINPROD_ANDROID:
      //
      // Enroll Android KEK certificate
      //
      Status = EnrollX509FromFV(
                     &KEK_ANDROID_Guid,
                     EFI_KEY_EXCHANGE_KEY_NAME,
                     &gEfiGlobalVariableGuid,
                     &AND_OwnerGuid
                     );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
      Status = EnrollX509FromFV(
                     &EFI_Flash_CertGuid,
                     EFI_IMAGE_SECURITY_DATABASE,
                     &gEfiImageSecurityDatabaseGuid,
                     &Intel_OwnerGuid
                     );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
      //
      // Enroll Android db certificate
      //
      Status = EnrollX509FromFV(
                     &db_ANDROID_Guid,
                     EFI_IMAGE_SECURITY_DATABASE,
                     &gEfiImageSecurityDatabaseGuid,
                     &AND_OwnerGuid
                     );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
      break;
    case SECURE_BOOT_PROFILE_WINPREPROD_ANDROID:
      Status = EnrollX509FromFV(
                     &KEK_ANDROID_Guid,
                     EFI_KEY_EXCHANGE_KEY_NAME,
                     &gEfiGlobalVariableGuid,
                     &AND_OwnerGuid
                     );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
      //
      // Enroll INTEL EFI Tool db certificate
      //
      Status = EnrollX509FromFV(
                     &EFI_Flash_CertGuid,
                     EFI_IMAGE_SECURITY_DATABASE,
                     &gEfiImageSecurityDatabaseGuid,
                     &Intel_OwnerGuid
                     );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
      //
      // Enroll Android db certificate
      //
      Status = EnrollX509FromFV(
                     &db_ANDROID_Guid,
                     EFI_IMAGE_SECURITY_DATABASE,
                     &gEfiImageSecurityDatabaseGuid,
                     &AND_OwnerGuid
                     );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
      break;
  }

  ON_EXIT:
    if (EFI_ERROR (Status)) {
      RemoveAllSecureBootKeys();
    }

  UpdateCustomMode (STANDARD_SECURE_BOOT_MODE);

  return Status;
}

